package com.cmsz.centralservice.utils;

import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SCPClient;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.Charset;

/**
 * description : ssh工具类
 */
public class SshUtil implements AutoCloseable {

    /**
     * 默认5分钟执行超时
     */
    private static final long TIME_OUT = 5 * 60 * 1000;
    /**
     * ssh连接
     */
    private static Connection conn;

    public Connection getConnection() {
        return conn;
    }

    /**
     * 用户密码方式  远程登录linux服务器
     *
     * @param hostname 服务器IP或主机名
     * @param username 用户名
     * @param password 登陆密码
     * @return ssh工具类实体
     * @throws IOException 连接异常
     */
    public static SshUtil build(String hostname, String username, String password) throws IOException {
        SshUtil sshUtil = new SshUtil();
        conn = sshUtil.login(hostname, 22, username, password);
        return sshUtil;
    }

    /**
     * 用户密码方式  远程登录linux服务器
     *
     * @param hostname 服务器IP或主机名
     * @param port     端口
     * @param username 用户名
     * @param password 登陆密码
     * @return ssh工具类实体
     * @throws IOException 连接异常
     */
    public static SshUtil build(String hostname, int port, String username, String password) throws IOException {
        SshUtil sshUtil = new SshUtil();
        conn = sshUtil.login(hostname, port, username, password);
        return sshUtil;
    }

    /**
     * 秘钥方式  远程登录linux服务器
     *
     * @param hostname 服务器IP或主机名
     * @param username 用户名
     * @param pemFile  密钥文件字符数组，其中包含用户的DSA或RSA私钥
     * @return ssh工具类实体
     * @throws IOException 连接异常
     */
    public static SshUtil build(String hostname, String username, File pemFile) throws IOException {
        SshUtil sshUtil = new SshUtil();
        conn = sshUtil.login(hostname, 22, username, pemFile, null);
        return sshUtil;
    }

    /**
     * 秘钥方式  远程登录linux服务器
     *
     * @param hostname 服务器IP或主机名
     * @param username 用户名
     * @param pemFile  密钥文件字符数组，其中包含用户的DSA或RSA私钥
     * @param password 如果秘钥字符数组加密  需要用该字段解密  否则不需要可以为null
     * @return ssh工具类实体
     * @throws IOException 连接异常
     */
    public static SshUtil build(String hostname, String username, File pemFile, String password) throws IOException {
        SshUtil sshUtil = new SshUtil();
        conn = sshUtil.login(hostname, 22, username, pemFile, password);
        return sshUtil;
    }

    /**
     * 秘钥方式  远程登录linux服务器
     *
     * @param hostname 服务器IP或主机名
     * @param port     端口
     * @param username 用户名
     * @param pemFile  密钥文件字符数组，其中包含用户的DSA或RSA私钥
     * @return ssh工具类实体
     * @throws IOException 连接异常
     */
    public static SshUtil build(String hostname, int port, String username, File pemFile) throws IOException {
        SshUtil sshUtil = new SshUtil();
        conn = sshUtil.login(hostname, port, username, pemFile, null);
        return sshUtil;
    }

    /**
     * 秘钥方式  远程登录linux服务器
     *
     * @param hostname 服务器IP或主机名
     * @param port     端口
     * @param username 用户名
     * @param pemFile  密钥文件字符数组，其中包含用户的DSA或RSA私钥
     * @param password 如果秘钥字符数组加密  需要用该字段解密  否则不需要可以为null
     * @return ssh工具类实体
     * @throws IOException 连接异常
     */
    public static SshUtil build(String hostname, int port, String username, File pemFile, String password) throws IOException {
        SshUtil sshUtil = new SshUtil();
        conn = sshUtil.login(hostname, port, username, pemFile, password);
        return sshUtil;
    }


    /**
     * 用户密码方式  远程登录linux服务器
     *
     * @param hostname 服务器IP或主机名
     * @param port     端口
     * @param username 用户名
     * @param password 登陆密码
     * @return 连接
     * @throws IOException 连接异常
     */
    public Connection login(String hostname, int port, String username, String password) throws IOException {
        //获取连接
        Connection conn = new Connection(hostname, port);
        //连接
        conn.connect();
        //输入账号密码登陆
        boolean isAuthenticated = conn.authenticateWithPassword(username, password);
        //登陆失败，返回错误
        if (!isAuthenticated) {
            throw new IOException("isAuthentication failed.");
        }
        return conn;
    }

    /**
     * 秘钥方式  远程登录linux服务器
     *
     * @param hostname 服务器IP或主机名
     * @param port     端口
     * @param username 用户名
     * @param pemFile  密钥文件字符数组，其中包含用户的DSA或RSA私钥
     * @param password 如果秘钥字符数组加密  需要用该字段解密  否则不需要可以为null
     * @return 连接
     * @throws IOException 连接异常
     */
    public Connection login(String hostname, int port, String username, File pemFile, String password) throws IOException {
        //获取连接
        Connection conn = new Connection(hostname, port);
        //连接
        conn.connect();
        //输入账号 私钥路径
        boolean isAuthenticated = conn.authenticateWithPublicKey(username, pemFile, password);
        //登陆失败，返回错误
        if (!isAuthenticated) {
            throw new IOException("isAuthentication failed.");
        }
        return conn;
    }

    /**
     * scp文件远程拷贝
     *
     * @param localFile 本地文件全路径
     * @param remoteDir 拷贝的远程目录
     * @throws IOException 文件传输异常
     */
    public static void put(String localFile, String remoteDir) throws IOException {
        SCPClient client = conn.createSCPClient();
        client.put(localFile, remoteDir, "0754");
    }

    /**
     * scp文件远程拷贝
     *
     * @param localFile       本地文件全路径
     * @param remoteDir       拷贝的远程目录
     * @param permissionsCode 文件权限(如:"0755")
     * @throws IOException 文件传输异常
     */
    public static void put(String localFile, String remoteDir, String permissionsCode) throws IOException {
        SCPClient client = conn.createSCPClient();
        client.put(localFile, remoteDir, permissionsCode);
    }

    /**
     * 从远程服务器拷贝文件到本地
     *
     * @param remoteFile 远程文件全路径
     * @param localDir   本地目录
     * @throws IOException 文件传输异常
     */
    public static void get(String remoteFile, String localDir) throws IOException {
        SCPClient client = conn.createSCPClient();
        client.get(remoteFile, localDir);
    }

    /**
     * 执行脚本或命令
     *
     * @param cmd 脚本或命令
     * @return 返回结果
     */
    public String exec(String cmd) throws IOException {
        String result;
        Session session = null;
        try {
            session = conn.openSession();
            // 建立虚拟终端
            session.requestPTY("bash");
            // 打开一个Shell
            session.startShell();
            // 准备输入命令
            PrintWriter out = new PrintWriter(session.getStdin());
            // 输入待执行命令
            out.println(cmd);
            out.println("exit");
            out.close();
            // 7. 等待，除非1.连接关闭；2.输出数据传送完毕；3.进程状态为退出；4.超时
            session.waitForCondition(ChannelCondition.CLOSED | ChannelCondition.EOF | ChannelCondition.EXIT_STATUS, TIME_OUT);
            result = collectStdout(session.getStdout(), Charset.defaultCharset().toString());
            // 正常执行完成返回0
            if (session.getExitStatus() != 0) {
                result += "命令执行异常信息:"+System.getProperty("line.separator");
                result += collectStdout(session.getStderr(), Charset.defaultCharset().toString());
            }
        } finally {
            close(session);
        }
        return result;
    }

    /**
     * 收集执行输出的返回结果
     *
     * @param in      输入流对象
     * @param charset 编码
     * @return String 以纯文本的格式返回
     */
    public static String collectStdout(InputStream in, String charset) throws IOException {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new StreamGobbler(in), charset))) {
            StringBuilder buffer = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                buffer.append(line).append(System.getProperty("line.separator"));
            }
            return buffer.toString();
        }
    }

    @Override
    public void close() throws Exception {
        if (conn != null) {
            conn.close();
        }
    }

    private static void close(Session session) {
        if (session != null) {
            session.close();
        }
    }

}
